﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.DataVisualization.Charting;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using GraphEntities;
using NumericalData.Statistics;
using NumericalData.Systems;
using WPFSolution.CommonOperations;
using NumericalData.SolvationMethods;
using NumericalData.SolvationMethods.Async;
using Point = GraphEntities.Point;

namespace WPFSolution.UserControls
{
    /// <summary>
    /// Interaction logic for Statistics.xaml
    /// </summary>
    public partial class Statistics : UserControl
    {
        private CancellationTokenSource _cancelSource;

        #region Dependency Properties
        public static readonly DependencyProperty StatusStringProperty =
           DependencyProperty.Register("StatusString", typeof(string), typeof(Statistics), new PropertyMetadata(default(string)));
        /// <summary>
        /// Current Control Status
        /// </summary>
        public string StatusString
        {
            get { return (string)GetValue(StatusStringProperty); }
            set { SetValue(StatusStringProperty, value); }
        }

        public static readonly DependencyProperty IsBusyProperty =
            DependencyProperty.Register("IsBusy", typeof(bool), typeof(Statistics), new PropertyMetadata(default(bool)));
        /// <summary>
        /// Calculation Status
        /// </summary>
        public bool IsBusy
        {
            get { return (bool)GetValue(IsBusyProperty); }
            set { SetValue(IsBusyProperty, value); }
        }
        #endregion

        public Statistics()
        {
            InitializeComponent();
            ChangeStatus("");
        }

        #region Status
        void SetCalculationStatus(string status, int progress)
        {
            CalculationProgressBar.Value = progress;
            CalculationStatusTextBlock.Text = status;
        }

        private void ChangeStatus(string newStatus)
        {
            if (newStatus == "")
            {
                StatusString = "Statistics Control Is Ready";
                IsBusy = false;
            }
            else
            {
                StatusString = newStatus;
                IsBusy = true;
            }
        }

        void ToggleEnablenessParametersPanel()
        {
            if (CompareOptionsGroupBox != null)
            {
                if (!CompareOptionsGroupBox.IsEnabled)
                {
                    CompareOptionsGroupBox.IsEnabled = true;
                    ChangeStatus("");
                    SetCalculationStatus("Calculated!", 0);
                    CancelButton.Visibility = Visibility.Collapsed;
                }
                else
                {
                    CompareOptionsGroupBox.IsEnabled = false;
                    ChangeStatus("Busy, Calculating");
                    //CancelButton.Visibility = Visibility.Visible;
                }
            }
        }

        private void RefreshCalculationStatus(int currentDim, int currentIteration, object context)
        {
            if (context != null)
            {
                var uicontext = context as SynchronizationContext;
                if (_cancelSource == null)
                    Thread.CurrentThread.Abort();
                if (uicontext != null)
                    uicontext.Send(d =>
                    {
                        double rangeLength = GetMaxDimention() - GetMinDimention();
                        double currPoint = currentDim - GetMinDimention();
                        int currentProgress = (int)(currPoint * 100.0 / rangeLength);
                        string currentStatus =
                            "Calculating:\t" + "\n" +
                            "Current Dimention:\t" + currentDim + "\n" +
                            "Current Iteration:\t" + currentIteration + "\n" +
                            "Current Progress:\t" + currentProgress + "%";
                        SetCalculationStatus(currentStatus, currentProgress);
                    }, null);
            }
        }
        #endregion


        #region TextBoxes GetValues Methods
        double GetAccuracy()
        {
            return GUIManipulator.GetDoubleValue(accuracyTextBox, 0.001, 0.01, 0.000001);
        }

        int GetMaximumIterations()
        {
            return GUIManipulator.GetIntValue(maximumIterationsTextBox, 10000, 100000, 1000);
        }

        int GetMinDimention()
        {
            return GUIManipulator.GetIntValue(minDimentionTextBox, 3, 99, 3);
        }

        int GetMaxDimention()
        {
            return GUIManipulator.GetIntValue(maxDimentionTextBox, 20, 100, 4);
        }

        int GetIterationsPerDimention()
        {
            return GUIManipulator.GetIntValue(IterationsPerDimTextBox, 5, 20, 1);
        }


        #endregion



        #region Fields Events
        private void AccuracyTextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            if (accuracyTextBox != null)
            {
                accuracyTextBox.Text = GetAccuracy().ToString();
            }
        }

        private void MaximumIterationsTextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            if (maximumIterationsTextBox != null)
            {
                maximumIterationsTextBox.Text = GetMaximumIterations().ToString();
            }
        }

        private void IterationsPerDimSlider_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (IterationsPerDimSlider != null)
            {
                var wonderValue = GetIterationsPerDimention();
                var maxDim = GetMaxDimention();
                if (wonderValue > 5 && maxDim > 30)
                {
                    IterationsPerDimSlider.Value = 5;
                    MessageBox.Show("Computation On Matrices Of Large Dimension" +
                                    " Can Lead To Large Delays." +
                                    "\nNumber Of Iterations Per Dimention Reduced",
                                    "Attention", MessageBoxButton.OK, MessageBoxImage.Warning);
                }

            }
        }
        #endregion


        private void BuldStatisticsButton_Click(object sender, RoutedEventArgs e)
        {
            ProcessCalculationResult();
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            if (_cancelSource != null)
            {
                _cancelSource.Cancel();
            }
        }


        #region Functions Execution
        private List<TraceableIterationMethod> GetFunctionsListFromListBox()
        {
            if (MethodsListBox != null)
            {
                return (from ListBoxItem item
                                         in MethodsListBox.SelectedItems
                        select GetFunctionByMethodName(item.Name)).ToList();

            }
            return null;
        }

        private static TraceableIterationMethod GetFunctionByMethodName(string name)
        {
            switch (name)
            {
                case ("YakobiMethodItem"):
                    return new YakobiImplicitMethod();
                case ("SimpleMethodItem"):
                    return new SimpleExplicitSolvationMethod();
                case ("AdaptiveMethodItem"):
                    return new AdaptiveExplicitSolvationMethod();
                default:
                    throw new Exception("Unknown Method!");
            }
        }
        #endregion


        #region Calculation Process
        private void ProcessCalculationResult()
        {
            var minDimention = GetMinDimention();
            var maxDimention = GetMaxDimention();
            var itersPerDim = GetIterationsPerDimention();
            var maxIterations = GetMaximumIterations();
            var accur = GetAccuracy();
            var methodsToCompare = GetFunctionsListFromListBox();
            if (methodsToCompare == null) return;

            ToggleEnablenessParametersPanel();
            _cancelSource = SystemsSolvationsStatistics.AsyncGetStatisticsForFunctions(
                minDimention, maxDimention, itersPerDim, accur, maxIterations, false,
                RefreshCalculationStatus, SynchronizationContext.Current, methodsToCompare.ToArray(),
                BuildChartsForResult,
                GotException,
                Canceled
                );
        }

        private void GotException(Exception exc)
        {
            MessageBox.Show("Caught exception!", "An error occurred!",
                MessageBoxButton.OK, MessageBoxImage.Error);
            ToggleEnablenessParametersPanel();
        }

        private void Canceled(OperationCanceledException exc)
        {
            if (_cancelSource != null)
            {
                MessageBox.Show("Process Canceled!" + exc.Message, "Process Canceled!",
                    MessageBoxButton.OK, MessageBoxImage.Information);
                ToggleEnablenessParametersPanel();
                //Thread.CurrentThread.Abort();
                //exc.CancellationToken.ThrowIfCancellationRequested();
            }
        }

        private void BuildChartsForResult(StatisticsContainer[] allstats)
        {
            BuildGraphics(allstats);
            ToggleEnablenessParametersPanel();
        }
        #endregion

        #region Charts
        private void BuildGraphics(StatisticsContainer[] allstats)
        {
            ClearAllCharts();
            var propretiesCount = allstats[0].NumberOfParameters;
            for (int i = 0; i < propretiesCount; ++i)
            {
                var tab = BuildChartTab(allstats, i);
                StatisticsTabControl.Items.Add(tab);
            }
            StatisticsTabControl.SelectedIndex = 1;
        }

        private TabItem BuildChartTab(StatisticsContainer[] allstats, int viewingPropertyIndex)
        {
            var propertyName = StatisticsContainer.GetPropNameFromStatTuple(allstats[0][viewingPropertyIndex]);
            var tab = CreateNewTabWithChart(propertyName);
            var chart = (Chart)tab.Content;
            FillChartWithStatistics(chart, allstats, viewingPropertyIndex);
            return tab;
        }

        private TabItem CreateNewTabWithChart(string name)
        {
            var tab = new TabItem();
            tab.Header = name;
            var chart = new Chart();
            chart.Background = (Brush)Resources.FindName("chartGraphBackGroundBrush");
            chart.ChartAreaStyle = (Style)FindName("chartAreaStyle");
            chart.Title = name + "/Dimention Dependence";
            chart.Axes.Add(
                new LinearAxis
                    {
                        AxisLabelStyle = (Style)Resources.FindName("NumericAxisLabel"),
                        Orientation = AxisOrientation.X,
                        Title = "Dimentions",
                        ShowGridLines = true
                    });
            chart.Axes.Add(
                new LinearAxis
                    {
                        Orientation = AxisOrientation.Y,
                        ShowGridLines = true,
                        Title = "Average " + name
                    });
            tab.Content = chart;
            return tab;
        }

        private void FillChartWithStatistics(Chart chart, StatisticsContainer[] allstats, int viewingPropertyIndex)
        {
            foreach (StatisticsContainer statisticsContainer in allstats)
            {
                DrawLines(chart, statisticsContainer, viewingPropertyIndex);
            }
        }

        private void ClearAllCharts()
        {
            while (StatisticsTabControl.Items.Count > 1)
            {
                StatisticsTabControl.Items.RemoveAt(1);
            }
        }

        private void DrawLines(Chart chart, StatisticsContainer statisticsContainer, int viewingPropertyIndex)
        {
            var averageValues =
                StatisticsContainer.GetAvgValuesFromStatTuple(statisticsContainer[viewingPropertyIndex]);
            AddLineToTheChart(chart, averageValues, statisticsContainer.MethodName);
            if (ShowVarianceLines.IsChecked != null && ShowVarianceLines.IsChecked.Value)
            {
                var dispValues = 
                    StatisticsContainer.GetDispValuesFromStatTuple(statisticsContainer[viewingPropertyIndex]);
                AddVarianceLines(chart, averageValues, dispValues, statisticsContainer.MethodName);
            }
        }

        private void AddVarianceLines(Chart chart, Points avgValues, Points dispValues, string propertyName)
        {
            var upperPoints = new Points();
            var lowerPoints = new Points();
            for (var i = 0; i < avgValues.Count - 1; ++i)
            {
                upperPoints.Add(new Point(avgValues[i].X, avgValues[i].Y + dispValues[i].Y));
                lowerPoints.Add(new Point(avgValues[i].X, avgValues[i].Y - dispValues[i].Y));
            }
            AddLineToTheChart(chart, upperPoints, propertyName + " Variance");
            AddLineToTheChart(chart, lowerPoints, propertyName + " Variance");
        }

        private void AddLineToTheChart(Chart chart, Points data, string legend)
        {
            var seria = new LineSeries
            {
                Title = legend,
                IndependentValueBinding = new Binding("X"),
                DependentValueBinding = new Binding("Y"),
                ItemsSource = data
            };
            chart.Series.Add(seria);
        }
        #endregion

        private void MethodsListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (MethodsListBox.SelectedItems.Count > 1)
            {
                ShowVarianceLines.IsChecked = false;
            }
        }

    }
}
